ν¬λ‘μ€ μ¬μ΄νΈ μ€ν¬λ¦½ν (XSS) 곡격μ λ°©μ΄νκ³ κ°λ ₯ν νλ‘ νΈμλ 보μμ μν΄ μ½ν μΈ λ³΄μ μ μ± (CSP)μ ꡬννλ λ°©λ²μ λν μ’ ν© κ°μ΄λμ λλ€.
νλ‘ νΈμλ 보μ: XSS λ°©μ΄μ μ½ν μΈ λ³΄μ μ μ± (CSP)
μ€λλ μ μΉ κ°λ° νκ²½μμ νλ‘ νΈμλ 보μμ λ§€μ° μ€μν©λλ€. μΉ μ ν리μΌμ΄μ μ΄ μ μ λ 볡μ‘ν΄μ§κ³ μνΈμμ©μ΄ λ§μμ§λ©΄μ, λ€μν 곡격, νΉν ν¬λ‘μ€ μ¬μ΄νΈ μ€ν¬λ¦½ν (XSS)μ λ μ·¨μ½ν΄μ§κ³ μμ΅λλ€. μ΄ κΈμμλ XSS μ·¨μ½μ μ μ΄ν΄νκ³ μννλ λ°©λ²κ³Ό κ°λ ₯ν λ°©μ΄ λ©μ»€λμ¦μΌλ‘μ μ½ν μΈ λ³΄μ μ μ± (CSP)μ ꡬννλ λ°©λ²μ λν μ’ ν©μ μΈ κ°μ΄λλ₯Ό μ 곡ν©λλ€.
ν¬λ‘μ€ μ¬μ΄νΈ μ€ν¬λ¦½ν (XSS) μ΄ν΄νκΈ°
XSSλ 무μμΈκ°?
ν¬λ‘μ€ μ¬μ΄νΈ μ€ν¬λ¦½ν (XSS)μ μ λ’°ν μ μλ μ μμ μΈ μΉμ¬μ΄νΈμ μ μ± μ€ν¬λ¦½νΈκ° μ£Όμ λλ μΈμ μ 곡격μ ν μ νμ λλ€. XSS 곡격μ 곡격μκ° μΉ μ ν리μΌμ΄μ μ μ¬μ©νμ¬ λ€λ₯Έ μ΅μ’ μ¬μ©μμκ² μΌλ°μ μΌλ‘ λΈλΌμ°μ μΈ‘ μ€ν¬λ¦½νΈ ννμ μ μ± μ½λλ₯Ό μ μ‘ν λ λ°μν©λλ€. μ΄λ¬ν 곡격μ νμ©νλ κ²°ν¨μ λ§€μ° λ리 νΌμ Έ μμΌλ©°, μΉ μ ν리μΌμ΄μ μ΄ μ¬μ©μλ‘λΆν° λ°μ μ λ ₯μ κ²μ¦νκ±°λ μΈμ½λ©νμ§ μκ³ μμ±νλ μΆλ ₯ λ΄μμ μ¬μ©νλ λͺ¨λ κ³³μμ λ°μν μ μμ΅λλ€.
μ¬μ©μλ€μ΄ λκΈμ κ²μν μ μλ μΈκΈ° μλ μ¨λΌμΈ ν¬λΌμ μμν΄ λ³΄μΈμ. λ§μ½ ν¬λΌμ΄ μ¬μ©μ μ λ ₯μ μ λλ‘ μ²λ¦¬νμ§ μλλ€λ©΄, 곡격μλ λκΈμ μ μ± μλ°μ€ν¬λ¦½νΈ μ€λν«μ μ£Όμ ν μ μμ΅λλ€. λ€λ₯Έ μ¬μ©μκ° ν΄λΉ λκΈμ λ³Ό λ, μ μ± μ€ν¬λ¦½νΈκ° κ·Έλ€μ λΈλΌμ°μ μμ μ€νλμ΄ μΏ ν€λ₯Ό νμΉκ±°λ, νΌμ± μ¬μ΄νΈλ‘ 리λλ μ νκ±°λ, μΉμ¬μ΄νΈλ₯Ό λ³μ‘°ν μ μμ΅λλ€.
XSS 곡격μ μ ν
- λ°μ¬ν XSS(Reflected XSS): μ μ± μ€ν¬λ¦½νΈκ° λ¨μΌ μμ²μ μ£Όμ λ©λλ€. μλ²λ HTTP μμ²μμ μ£Όμ λ λ°μ΄ν°λ₯Ό μ½κ³ μ¬μ©μμκ² λ€μ λ°μ¬νμ¬ μ¬μ©μμ λΈλΌμ°μ μμ μ€ν¬λ¦½νΈλ₯Ό μ€νμν΅λλ€. μ΄λ μ’ μ’ μ μ± λ§ν¬κ° ν¬ν¨λ νΌμ± μ΄λ©μΌμ ν΅ν΄ μ΄λ£¨μ΄μ§λλ€.
- μ μ₯ν XSS(Stored XSS): μ μ± μ€ν¬λ¦½νΈκ° λμ μλ²(μ: λ°μ΄ν°λ² μ΄μ€, ν¬λΌ κ²μλ¬Ό, λκΈ μΉμ )μ μ μ₯λ©λλ€. λ€λ₯Έ μ¬μ©μκ° μ μ₯λ λ°μ΄ν°μ μ κ·Όν λ μ€ν¬λ¦½νΈκ° κ·Έλ€μ λΈλΌμ°μ μμ μ€νλ©λλ€. μ΄ μ νμ XSSλ λ€μμ μ¬μ©μμκ² μν₯μ μ€ μ μμ΄ νΉν μνν©λλ€.
- DOM κΈ°λ° XSS(DOM-based XSS): μ·¨μ½μ μ΄ ν΄λΌμ΄μΈνΈ μΈ‘ μλ°μ€ν¬λ¦½νΈ μ½λ μ체μ μ‘΄μ¬ν©λλ€. 곡격μ ν¬μμμ λΈλΌμ°μ μμ DOM(λ¬Έμ κ°μ²΄ λͺ¨λΈ)μ μ‘°μνμ¬ μ μ± μ€ν¬λ¦½νΈλ₯Ό μ€νμν΅λλ€. μ΄λ μ’ μ’ URLμ΄λ λ€λ₯Έ ν΄λΌμ΄μΈνΈ μΈ‘ λ°μ΄ν°λ₯Ό μ‘°μνλ κ²μ ν¬ν¨ν©λλ€.
XSSμ μν₯
μ±κ³΅μ μΈ XSS 곡격μ κ²°κ³Όλ μ¬κ°ν μ μμ΅λλ€:
- μΏ ν€ νμ·¨: 곡격μλ μ¬μ©μ μΏ ν€λ₯Ό νμ³ κ³μ λ° λ―Όκ°ν μ 보μ μ κ·Όν μ μμ΅λλ€.
- κ³μ νμ΄μ¬νΉ: νμΉ μΏ ν€λ‘ 곡격μλ μ¬μ©μλ₯Ό μ¬μΉνκ³ λμ νμ¬ νλμ μνν μ μμ΅λλ€.
- μΉμ¬μ΄νΈ λ³μ‘°: 곡격μλ μΉμ¬μ΄νΈμ μΈκ΄μ μμ νμ¬ νμ μ 보λ₯Ό νΌλ¨λ¦¬κ±°λ λΈλλμ λͺ μ±μ νΌμν μ μμ΅λλ€.
- νΌμ± μ¬μ΄νΈλ‘ 리λλ μ : μ¬μ©μλ λ‘κ·ΈμΈ μ 보λ₯Ό νμΉκ±°λ λ©μ¨μ΄λ₯Ό μ€μΉνλ μ μ± μΉμ¬μ΄νΈλ‘ 리λλ μ λ μ μμ΅λλ€.
- λ°μ΄ν° μ μΆ: νμ΄μ§μ νμλλ λ―Όκ°ν λ°μ΄ν°κ° λλλΉνμ¬ κ³΅κ²©μμ μλ²λ‘ μ μ‘λ μ μμ΅λλ€.
XSS λ°©μ΄ κΈ°λ²
XSS 곡격μ λ°©μ΄νκΈ° μν΄μλ μ λ ₯κ° κ²μ¦κ³Ό μΆλ ₯ μΈμ½λ© λͺ¨λμ μ΄μ μ λ§μΆ λ€μΈ΅μ μΈ μ κ·Ό λ°©μμ΄ νμν©λλ€.
μ λ ₯κ° κ²μ¦
μ λ ₯κ° κ²μ¦μ μ¬μ©μ μ λ ₯μ΄ μμλλ νμκ³Ό λ°μ΄ν° μ νμ λ°λ₯΄λμ§ νμΈνλ κ³Όμ μ λλ€. XSSμ λν μλ²½ν λ°©μ΄μ± μ μλμ§λ§, 곡격 νλ©΄μ μ€μ΄λ λ° λμμ΄ λ©λλ€.
- νμ΄νΈλ¦¬μ€νΈ κ²μ¦: νμ©λλ λ¬Έμ λ° ν¨ν΄μ μ격ν μ§ν©μ μ μν©λλ€. νμ΄νΈλ¦¬μ€νΈμ μΌμΉνμ§ μλ λͺ¨λ μ λ ₯μ κ±°λΆν©λλ€. μλ₯Ό λ€μ΄, μ¬μ©μκ° μ΄λ¦μ μ λ ₯ν κ²μΌλ‘ μμλλ€λ©΄ κΈμ, 곡백, κ·Έλ¦¬κ³ κ²½μ°μ λ°λΌ νμ΄νλ§ νμ©ν©λλ€.
- λΈλ리μ€νΈ κ²μ¦: μλ €μ§ μ μ± λ¬Έμλ ν¨ν΄μ μλ³νκ³ μ°¨λ¨ν©λλ€. κ·Έλ¬λ λΈλ리μ€νΈλ μ’ μ’ λΆμμ νλ©° κ΅λ¬ν 곡격μμ μν΄ μ°νλ μ μμ΅λλ€. μΌλ°μ μΌλ‘ λΈλ리μ€νΈ κ²μ¦λ³΄λ€ νμ΄νΈλ¦¬μ€νΈ κ²μ¦μ΄ μ νΈλ©λλ€.
- λ°μ΄ν° μ ν κ²μ¦: μ λ ₯μ΄ μμλλ λ°μ΄ν° μ ν(μ: μ μ, μ΄λ©μΌ μ£Όμ, URL)κ³Ό μΌμΉνλμ§ νμΈν©λλ€.
- κΈΈμ΄ μ ν: λ²νΌ μ€λ²νλ‘μ° μ·¨μ½μ μ λ°©μ§νκΈ° μν΄ μ λ ₯ νλμ μ΅λ κΈΈμ΄ μ νμ λ‘λλ€.
μμ (PHP):
<?php
$username = $_POST['username'];
// νμ΄νΈλ¦¬μ€νΈ κ²μ¦: μλ¬Έ, μ«μ, λ°μ€λ§ νμ©
if (preg_match('/^[a-zA-Z0-9_]+$/', $username)) {
// μ ν¨ν μ¬μ©μ μ΄λ¦
echo "μ ν¨ν μ¬μ©μ μ΄λ¦: " . htmlspecialchars($username, ENT_QUOTES, 'UTF-8');
} else {
// μ ν¨νμ§ μμ μ¬μ©μ μ΄λ¦
echo "μλͺ»λ μ¬μ©μ μ΄λ¦μ
λλ€. μλ¬Έ, μ«μ, λ°μ€λ§ νμ©λ©λλ€.";
}
?>
μΆλ ₯ μΈμ½λ© (μ΄μ€μΌμ΄ν)
μ΄μ€μΌμ΄νμ΄λΌκ³ λ μλ €μ§ μΆλ ₯ μΈμ½λ©μ νΉμ λ¬Έμλ₯Ό HTML μν°ν° λλ URL μΈμ½λ©λ ννλ‘ λ³ννλ κ³Όμ μ λλ€. μ΄λ λΈλΌμ°μ κ° ν΄λΉ λ¬Έμλ₯Ό μ½λλ‘ ν΄μνλ κ²μ λ°©μ§ν©λλ€.
- HTML μΈμ½λ©:
<,>,&,",'μ κ°μ΄ HTMLμμ νΉλ³ν μλ―Έλ₯Ό κ°λ λ¬Έμλ₯Ό μ΄μ€μΌμ΄νν©λλ€. PHPμhtmlspecialchars()μ κ°μ ν¨μλ λ€λ₯Έ μΈμ΄μ λλ±ν λ©μλλ₯Ό μ¬μ©ν©λλ€. - URL μΈμ½λ©: 곡백, μ¬λμ, λ¬Όμνμ κ°μ΄ URLμμ νΉλ³ν μλ―Έλ₯Ό κ°λ λ¬Έμλ₯Ό μΈμ½λ©ν©λλ€. PHPμ
urlencode()μ κ°μ ν¨μλ λ€λ₯Έ μΈμ΄μ λλ±ν λ©μλλ₯Ό μ¬μ©ν©λλ€. - μλ°μ€ν¬λ¦½νΈ μΈμ½λ©: μμλ°μ΄ν, ν°λ°μ΄ν, λ°±μ¬λμμ κ°μ΄ μλ°μ€ν¬λ¦½νΈμμ νΉλ³ν μλ―Έλ₯Ό κ°λ λ¬Έμλ₯Ό μ΄μ€μΌμ΄νν©λλ€.
JSON.stringify()μ κ°μ ν¨μλESAPI(Encoder)μ κ°μ λΌμ΄λΈλ¬λ¦¬λ₯Ό μ¬μ©ν©λλ€.
μμ (μλ°μ€ν¬λ¦½νΈ - HTML μΈμ½λ©):
function escapeHTML(str) {
let div = document.createElement('div');
div.appendChild(document.createTextNode(str));
return div.innerHTML;
}
let userInput = '<script>alert("XSS");</script>';
let encodedInput = escapeHTML(userInput);
// μΈμ½λ©λ μ
λ ₯μ DOMμ μΆλ ₯
document.getElementById('output').innerHTML = encodedInput; // μΆλ ₯: <script>alert("XSS");</script>
μμ (νμ΄μ¬ - HTML μΈμ½λ©):
import html
user_input = '<script>alert("XSS");</script>'
encoded_input = html.escape(user_input)
print(encoded_input) # μΆλ ₯: <script>alert("XSS");</script>
컨ν μ€νΈ μΈμ μΈμ½λ©
μ¬μ©νλ μΈμ½λ© μ νμ λ°μ΄ν°κ° νμλλ 컨ν μ€νΈμ λ°λΌ λ€λ¦ λλ€. μλ₯Ό λ€μ΄, HTML μμ± λ΄μ λ°μ΄ν°λ₯Ό νμνλ κ²½μ° HTML μμ± μΈμ½λ©μ μ¬μ©ν΄μΌ ν©λλ€. μλ°μ€ν¬λ¦½νΈ λ¬Έμμ΄ λ΄μ λ°μ΄ν°λ₯Ό νμνλ κ²½μ° μλ°μ€ν¬λ¦½νΈ λ¬Έμμ΄ μΈμ½λ©μ μ¬μ©ν΄μΌ ν©λλ€.
μμ :
<input type="text" value="<?php echo htmlspecialchars($_GET['name'], ENT_QUOTES, 'UTF-8'); ?>">
μ΄ μμ μμλ URLμ name νλΌλ―Έν° κ°μ΄ input νλμ value μμ± λ΄μ νμλ©λλ€. htmlspecialchars() ν¨μλ name νλΌλ―Έν°μ λͺ¨λ νΉμ λ¬Έμκ° μ μ νκ² μΈμ½λ©λλλ‘ νμ¬ XSS 곡격μ λ°©μ§ν©λλ€.
ν νλ¦Ώ μμ§ μ¬μ©
λ§μ νλ μΉ νλ μμν¬μ ν νλ¦Ώ μμ§(μ: React, Angular, Vue.js, Twig, Jinja2)μ μλ μΆλ ₯ μΈμ½λ© λ©μ»€λμ¦μ μ 곡ν©λλ€. μ΄λ¬ν μμ§μ ν νλ¦Ώμμ λ³μκ° λ λλ§λ λ μλμΌλ‘ μ΄μ€μΌμ΄ννμ¬ XSS μ·¨μ½μ μ μνμ μ€μ¬μ€λλ€. νμ μ¬μ©νλ ν νλ¦Ώ μμ§μ λ΄μ₯ μ΄μ€μΌμ΄ν κΈ°λ₯μ μ¬μ©νμΈμ.
μ½ν μΈ λ³΄μ μ μ± (CSP)
CSPλ 무μμΈκ°?
μ½ν μΈ λ³΄μ μ μ± (CSP)μ ν¬λ‘μ€ μ¬μ΄νΈ μ€ν¬λ¦½ν (XSS) λ° λ°μ΄ν° μ£Όμ 곡격μ ν¬ν¨ν νΉμ μ νμ 곡격μ νμ§νκ³ μννλ λ° λμμ΄ λλ μΆκ°μ μΈ λ³΄μ κ³μΈ΅μ λλ€. CSPλ λΈλΌμ°μ κ° λ¦¬μμ€λ₯Ό λ‘λν μ μλ μμ€μ νμ΄νΈλ¦¬μ€νΈλ₯Ό μ μν μ μκ² ν¨μΌλ‘μ¨ μλν©λλ€. μ΄ νμ΄νΈλ¦¬μ€νΈμλ λλ©μΈ, νλ‘ν μ½, μ¬μ§μ΄ νΉμ URLκΉμ§ ν¬ν¨λ μ μμ΅λλ€.
κΈ°λ³Έμ μΌλ‘ λΈλΌμ°μ λ μΉ νμ΄μ§κ° λͺ¨λ μμ€μμ 리μμ€λ₯Ό λ‘λνλλ‘ νμ©ν©λλ€. CSPλ 리μμ€λ₯Ό λ‘λν μ μλ μμ€λ₯Ό μ ννμ¬ μ΄ κΈ°λ³Έ λμμ λ³κ²½ν©λλ€. λ§μ½ μΉμ¬μ΄νΈκ° νμ΄νΈλ¦¬μ€νΈμ μλ μμ€μμ 리μμ€λ₯Ό λ‘λνλ €κ³ μλνλ©΄, λΈλΌμ°μ λ ν΄λΉ μμ²μ μ°¨λ¨ν©λλ€.
CSPμ μλ λ°©μ
CSPλ μλ²μμ λΈλΌμ°μ λ‘ HTTP μλ΅ ν€λλ₯Ό μ μ‘νμ¬ κ΅¬νλ©λλ€. μ΄ ν€λμλ κ° μ§μμ΄κ° νΉμ 리μμ€ μ νμ λν μ μ± μ μ§μ νλ μ§μμ΄ λͺ©λ‘μ΄ ν¬ν¨λμ΄ μμ΅λλ€.
CSP ν€λ μμ :
Content-Security-Policy: default-src 'self'; script-src 'self' https://example.com; style-src 'self' https://cdn.example.com; img-src 'self' data:; font-src 'self';
μ΄ ν€λλ λ€μ μ μ± λ€μ μ μν©λλ€:
default-src 'self': μΉ νμ΄μ§μ λμΌν μΆμ²(λλ©μΈ)μμλ§ λ¦¬μμ€λ₯Ό λ‘λνλλ‘ νμ©ν©λλ€.script-src 'self' https://example.com: λμΌν μΆμ²μhttps://example.comμμλ§ μλ°μ€ν¬λ¦½νΈλ₯Ό λ‘λνλλ‘ νμ©ν©λλ€.style-src 'self' https://cdn.example.com: λμΌν μΆμ²μhttps://cdn.example.comμμλ§ CSSλ₯Ό λ‘λνλλ‘ νμ©ν©λλ€.img-src 'self' data:: λμΌν μΆμ²μ λ°μ΄ν° URI(base64λ‘ μΈμ½λ©λ μ΄λ―Έμ§)μμλ§ μ΄λ―Έμ§λ₯Ό λ‘λνλλ‘ νμ©ν©λλ€.font-src 'self': λμΌν μΆμ²μμλ§ ν°νΈλ₯Ό λ‘λνλλ‘ νμ©ν©λλ€.
CSP μ§μμ΄
λ€μμ κ°μ₯ μΌλ°μ μΌλ‘ μ¬μ©λλ CSP μ§μμ΄λ€μ λλ€:
default-src: λͺ¨λ 리μμ€ μ νμ λν κΈ°λ³Έ μ μ± μ μ€μ ν©λλ€.script-src: μλ°μ€ν¬λ¦½νΈλ₯Ό λ‘λν μ μλ μμ€λ₯Ό μ μν©λλ€.style-src: CSSλ₯Ό λ‘λν μ μλ μμ€λ₯Ό μ μν©λλ€.img-src: μ΄λ―Έμ§λ₯Ό λ‘λν μ μλ μμ€λ₯Ό μ μν©λλ€.font-src: ν°νΈλ₯Ό λ‘λν μ μλ μμ€λ₯Ό μ μν©λλ€.connect-src: ν΄λΌμ΄μΈνΈκ° μ°κ²°ν μ μλ μΆμ²λ₯Ό μ μν©λλ€(μ: WebSocket, XMLHttpRequest).media-src: μ€λμ€ λ° λΉλμ€λ₯Ό λ‘λν μ μλ μμ€λ₯Ό μ μν©λλ€.object-src: νλ¬κ·ΈμΈ(μ: Flash)μ λ‘λν μ μλ μμ€λ₯Ό μ μν©λλ€.frame-src: νλ μ(<frame>,<iframe>)μΌλ‘ ν¬ν¨λ μ μλ μΆμ²λ₯Ό μ μν©λλ€.base-uri: λ¬Έμμ<base>μμμμ μ¬μ©ν μ μλ URLμ μ νν©λλ€.form-action: νΌμ΄ μ μΆλ μ μλ URLμ μ νν©λλ€.upgrade-insecure-requests: λΈλΌμ°μ μκ² μμ νμ§ μμ μμ²(HTTP)μ μμ ν μμ²(HTTPS)μΌλ‘ μλ μ κ·Έλ μ΄λνλλ‘ μ§μν©λλ€.block-all-mixed-content: λΈλΌμ°μ κ° νΌν©λ μ½ν μΈ (HTTPSλ₯Ό ν΅ν΄ λ‘λλ HTTP μ½ν μΈ )λ₯Ό λ‘λνλ κ²μ λ°©μ§ν©λλ€.report-uri: CSP μ μ± μ΄ μλ°λ λ λΈλΌμ°μ κ° μλ° λ³΄κ³ μλ₯Ό μ μ‘ν΄μΌ ν URLμ μ§μ ν©λλ€.report-to: μλ° λ³΄κ³ μλ₯Ό λ³΄λΌ μλν¬μΈνΈλ₯Ό ν¬ν¨νλ `Report-To` ν€λμ μ μλ κ·Έλ£Ή μ΄λ¦μ μ§μ ν©λλ€. `report-uri`λ³΄λ€ λ νλμ μ΄κ³ μ μ°ν λ체μ¬μ λλ€.
CSP μμ€ λͺ©λ‘ κ°
κ° CSP μ§μμ΄λ νμ©λλ μΆμ²λ ν€μλλ₯Ό μ§μ νλ μμ€ κ° λͺ©λ‘μ λ°μ΅λλ€.
'self': μΉ νμ΄μ§μ λμΌν μΆμ²μ 리μμ€λ₯Ό νμ©ν©λλ€.'none': λͺ¨λ μΆμ²μ 리μμ€λ₯Ό νμ©νμ§ μμ΅λλ€.'unsafe-inline': μΈλΌμΈ μλ°μ€ν¬λ¦½νΈ λ° CSSλ₯Ό νμ©ν©λλ€. XSSμ λν 보νΈλ₯Ό μ½νμν€λ―λ‘ κ°λ₯ν ν νΌν΄μΌ ν©λλ€.'unsafe-eval':eval()λ° κ΄λ ¨ ν¨μμ μ¬μ©μ νμ©ν©λλ€. 보μ μ·¨μ½μ μ μ λ°ν μ μμΌλ―λ‘ μ΄ μμ νΌν΄μΌ ν©λλ€.'strict-dynamic': λ Όμ€λ ν΄μλ₯Ό ν΅ν΄ λ§ν¬μ μμ μ€ν¬λ¦½νΈμ λͺ μμ μΌλ‘ λΆμ¬λ μ λ’°κ° ν΄λΉ λ£¨νΈ μ€ν¬λ¦½νΈμ μν΄ λ‘λλλ λͺ¨λ μ€ν¬λ¦½νΈλ‘ μ νλμ΄μΌ ν¨μ μ§μ ν©λλ€.https://example.com: νΉμ λλ©μΈμ 리μμ€λ₯Ό νμ©ν©λλ€.*.example.com: νΉμ λλ©μΈμ λͺ¨λ νμ λλ©μΈ 리μμ€λ₯Ό νμ©ν©λλ€.data:: λ°μ΄ν° URI(base64λ‘ μΈμ½λ©λ μ΄λ―Έμ§)λ₯Ό νμ©ν©λλ€.mediastream:: `media-src`μ λν΄ `mediastream:` URIλ₯Ό νμ©ν©λλ€.blob:: `blob:` URI(λΈλΌμ°μ λ©λͺ¨λ¦¬μ μ μ₯λ μ΄μ§ λ°μ΄ν°μ μ¬μ©)λ₯Ό νμ©ν©λλ€.filesystem:: `filesystem:` URI(λΈλΌμ°μ μ μλλ°μ€ νμΌ μμ€ν μ μ μ₯λ νμΌμ μ κ·Όνλ λ° μ¬μ©)λ₯Ό νμ©ν©λλ€.nonce-{random-value}: μΌμΉνλnonceμμ±μ κ°μ§ μΈλΌμΈ μ€ν¬λ¦½νΈλ μ€νμΌμ νμ©ν©λλ€.sha256-{hash-value}: μΌμΉνλsha256ν΄μλ₯Ό κ°μ§ μΈλΌμΈ μ€ν¬λ¦½νΈλ μ€νμΌμ νμ©ν©λλ€.
CSP ꡬννκΈ°
CSPλ₯Ό ꡬννλ λ°©λ²μλ μ¬λ¬ κ°μ§κ° μμ΅λλ€:
- HTTP ν€λ: CSPλ₯Ό ꡬννλ κ°μ₯ μΌλ°μ μΈ λ°©λ²μ μλ² μλ΅μ
Content-Security-PolicyHTTP ν€λλ₯Ό μ€μ νλ κ²μ λλ€. - λ©ν νκ·Έ: CSPλ HTML λ¬Έμμ
<meta>νκ·Έλ₯Ό μ¬μ©νμ¬ μ μν μλ μμ΅λλ€. κ·Έλ¬λ μ΄ λ°©λ²μ λ μ μ°νλ©° μΌλΆ μ νμ΄ μμ΅λλ€(μ:frame-ancestorsμ§μμ΄λ₯Ό μ μνλ λ° μ¬μ©ν μ μμ).
μμ (HTTP ν€λλ₯Ό ν΅ν CSP μ€μ - Apache):
Apache μ€μ νμΌ(μ: .htaccess λλ httpd.conf)μ λ€μ μ€μ μΆκ°ν©λλ€:
Header set Content-Security-Policy "default-src 'self'; script-src 'self' https://example.com; style-src 'self' https://cdn.example.com; img-src 'self' data:; font-src 'self';"
μμ (HTTP ν€λλ₯Ό ν΅ν CSP μ€μ - Nginx):
Nginx μ€μ νμΌ(μ: nginx.conf)μ server λΈλ‘μ λ€μ μ€μ μΆκ°ν©λλ€:
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://example.com; style-src 'self' https://cdn.example.com; img-src 'self' data:; font-src 'self';";
μμ (λ©ν νκ·Έλ₯Ό ν΅ν CSP μ€μ ):
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://example.com; style-src 'self' https://cdn.example.com; img-src 'self' data:; font-src 'self';">
CSP ν μ€νΈνκΈ°
CSP ꡬνμ΄ μμλλ‘ μλνλμ§ ν
μ€νΈνλ κ²μ΄ μ€μν©λλ€. λΈλΌμ°μ κ°λ°μ λꡬλ₯Ό μ¬μ©νμ¬ Content-Security-Policy ν€λλ₯Ό κ²μ¬νκ³ μλ° μ¬νμ΄ μλμ§ νμΈν μ μμ΅λλ€.
CSP 보κ³
`report-uri` λλ `report-to` μ§μμ΄λ₯Ό μ¬μ©νμ¬ CSP λ³΄κ³ λ₯Ό μ€μ ν©λλ€. μ΄λ₯Ό ν΅ν΄ μλ²λ CSP μ μ± μ΄ μλ°λ λ λ³΄κ³ μλ₯Ό λ°μ μ μμ΅λλ€. μ΄ μ 보λ 보μ μ·¨μ½μ μ μλ³νκ³ μμ νλ λ° λ§€μ° μ μ©ν μ μμ΅λλ€.
μμ (report-uriλ₯Ό μ¬μ©ν CSP):
Content-Security-Policy: default-src 'self'; report-uri /csp-report-endpoint;
μμ (report-toλ₯Ό μ¬μ©ν CSP - λ νλμ μΈ λ°©μ):
Report-To: {"group":"csp-endpoint","max_age":10886400,"endpoints":[{"url":"https://your-domain.com/csp-report-endpoint"}]}
Content-Security-Policy: default-src 'self'; report-to csp-endpoint;
μλ² μΈ‘ μλν¬μΈνΈ(μ΄ μμ μμλ `/csp-report-endpoint`)λ μ΄λ¬ν JSON λ³΄κ³ μλ₯Ό μμ νκ³ μ²λ¦¬νλλ‘ κ΅¬μ±λμ΄μΌ νλ©°, λμ€μ λΆμν μ μλλ‘ κΈ°λ‘ν΄μΌ ν©λλ€.
CSP λͺ¨λ² μ¬λ‘
- μ격ν μ μ±
μΌλ‘ μμνκΈ°: λμΌν μΆμ²μ 리μμ€λ§ νμ©νλ μ νμ μΈ μ μ±
(
default-src 'self')μΌλ‘ μμν©λλ€. νμμ λ°λΌ μ μ°¨ μ μ± μ μννκ³ νΉμ μμ€λ₯Ό μΆκ°ν©λλ€. 'unsafe-inline'λ°'unsafe-eval'νΌνκΈ°: μ΄ μ§μμ΄λ€μ XSSμ λν 보νΈλ₯Ό ν¬κ² μ½νμν΅λλ€. κ°λ₯ν ν μ¬μ©μ νΌνμΈμ. μΈλΌμΈ μ€ν¬λ¦½νΈ λ° μ€νμΌμ λν΄μλ λ Όμ€λ ν΄μλ₯Ό μ¬μ©νκ³ ,eval()μ¬μ©μ νΌνμΈμ.- μΈλΌμΈ μ€ν¬λ¦½νΈ λ° μ€νμΌμ λ Όμ€ λλ ν΄μ μ¬μ©νκΈ°: μΈλΌμΈ μ€ν¬λ¦½νΈλ μ€νμΌμ λ°λμ μ¬μ©ν΄μΌ νλ κ²½μ°, λ Όμ€λ ν΄μλ₯Ό μ¬μ©νμ¬ νμ΄νΈλ¦¬μ€νΈμ μΆκ°νμΈμ.
- CSP λ³΄κ³ μ¬μ©νκΈ°: μ μ± μ΄ μλ°λ λ μλ¦Όμ λ°λλ‘ CSP λ³΄κ³ λ₯Ό μ€μ νμΈμ. μ΄λ 보μ μ·¨μ½μ μ μλ³νκ³ μμ νλ λ° λμμ΄ λ©λλ€.
- CSP ꡬνμ μ² μ ν ν
μ€νΈνκΈ°: λΈλΌμ°μ κ°λ°μ λꡬλ₯Ό μ¬μ©νμ¬
Content-Security-Policyν€λλ₯Ό κ²μ¬νκ³ μλ° μ¬νμ΄ μλμ§ νμΈνμΈμ. - CSP μμ±κΈ° μ¬μ©νκΈ°: νΉμ μꡬ μ¬νμ λ°λΌ CSP ν€λλ₯Ό μμ±νλ λ° λμμ΄ λλ μ¬λ¬ μ¨λΌμΈ λκ΅¬κ° μμ΅λλ€.
- CSP λ³΄κ³ μ λͺ¨λν°λ§νκΈ°: μ κΈ°μ μΌλ‘ CSP λ³΄κ³ μλ₯Ό κ²ν νμ¬ μ μ¬μ μΈ λ³΄μ λ¬Έμ λ₯Ό μλ³νκ³ μ μ± μ κ°μ νμΈμ.
- CSPλ₯Ό μ΅μ μνλ‘ μ μ§νκΈ°: μΉμ¬μ΄νΈκ° λ°μ ν¨μ λ°λΌ 리μμ€ μμ‘΄μ±μ λ³κ²½ μ¬νμ λ°μνλλ‘ CSPλ₯Ό μ λ°μ΄νΈν΄μΌ ν©λλ€.
- μ½ν μΈ λ³΄μ μ μ± (CSP) λ¦°ν° μ¬μ© κ³ λ €νκΈ°: `csp-html-webpack-plugin`κ³Ό κ°μ λꡬλ λΈλΌμ°μ νμ₯ νλ‘κ·Έλ¨μ CSP ꡬμ±μ κ²μ¦νκ³ μ΅μ ννλ λ° λμμ΄ λ μ μμ΅λλ€.
- CSPλ₯Ό μ μ§μ μΌλ‘ μ μ©νκΈ° (λ³΄κ³ μ μ© λͺ¨λ): μ²μμλ `Content-Security-Policy-Report-Only` ν€λλ₯Ό μ¬μ©νμ¬ "λ³΄κ³ μ μ©" λͺ¨λλ‘ CSPλ₯Ό λ°°ν¬ν©λλ€. μ΄λ₯Ό ν΅ν΄ μ€μ λ‘ λ¦¬μμ€λ₯Ό μ°¨λ¨νμ§ μκ³ μ μ¬μ μΈ μ μ± μλ°μ λͺ¨λν°λ§ν μ μμ΅λλ€. λ³΄κ³ μλ₯Ό λΆμνμ¬ CSPλ₯Ό λ―ΈμΈ μ‘°μ ν ν κ°μ μ μ©ν©λλ€.
μμ (Nonce ꡬν):
μλ² μΈ‘ (Nonce μμ±):
<?php
$nonce = base64_encode(random_bytes(16));
?>
HTML:
<script nonce="<?php echo $nonce; ?>">
// μ¬κΈ°μ μΈλΌμΈ μ€ν¬λ¦½νΈ μμ±
console.log('Nonceλ₯Ό μ¬μ©ν μΈλΌμΈ μ€ν¬λ¦½νΈ');
</script>
CSP ν€λ:
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-<?php echo $nonce; ?>';
CSPμ μλνν° λΌμ΄λΈλ¬λ¦¬
μλνν° λΌμ΄λΈλ¬λ¦¬λ CDNμ μ¬μ©ν λλ ν΄λΉ λλ©μΈμ CSP μ μ±
μ ν¬ν¨μμΌμΌ ν©λλ€. μλ₯Ό λ€μ΄, CDNμμ jQueryλ₯Ό μ¬μ©νλ κ²½μ° CDNμ λλ©μΈμ script-src μ§μμ΄μ μΆκ°ν΄μΌ ν©λλ€.
νμ§λ§ μ 체 CDNμ 무ν±λκ³ νμ΄νΈλ¦¬μ€νΈμ μΆκ°νλ©΄ 보μ μνμ μ΄λν μ μμ΅λλ€. νμ 리μμ€ λ¬΄κ²°μ±(SRI)μ μ¬μ©νμ¬ CDNμμ λ‘λλ νμΌμ 무결μ±μ νμΈνλ κ²μ κ³ λ €νμΈμ.
νμ 리μμ€ λ¬΄κ²°μ± (SRI)
SRIλ CDNμ΄λ λ€λ₯Έ μλνν° μμ€μμ κ°μ Έμ¨ νμΌμ΄ λ³μ‘°λμ§ μμλμ§ λΈλΌμ°μ κ° νμΈν μ μκ² ν΄μ£Όλ 보μ κΈ°λ₯μ λλ€. SRIλ κ°μ Έμ¨ νμΌμ μνΈν ν΄μλ₯Ό μλ €μ§ ν΄μμ λΉκ΅νμ¬ μλν©λλ€. ν΄μκ° μΌμΉνμ§ μμΌλ©΄ λΈλΌμ°μ λ νμΌ λ‘λλ₯Ό μ°¨λ¨ν©λλ€.
μμ :
<script src="https://example.com/jquery.min.js" integrity="sha384-example-hash" crossorigin="anonymous"></script>
integrity μμ±μλ jquery.min.js νμΌμ μνΈν ν΄μκ° ν¬ν¨λ©λλ€. crossorigin μμ±μ λ€λ₯Έ μΆμ²μμ μ 곡λλ νμΌμ λν΄ SRIκ° μλνλ λ° νμν©λλ€.
κ²°λ‘
νλ‘ νΈμλ 보μμ μΉ κ°λ°μ μ€μν μΈ‘λ©΄μ λλ€. XSS λ°©μ΄ κΈ°λ²κ³Ό μ½ν μΈ λ³΄μ μ μ± (CSP)μ μ΄ν΄νκ³ κ΅¬νν¨μΌλ‘μ¨ κ³΅κ²©μ μνμ ν¬κ² μ€μ΄κ³ μ¬μ©μμ λ°μ΄ν°λ₯Ό 보νΈν μ μμ΅λλ€. μ λ ₯κ° κ²μ¦, μΆλ ₯ μΈμ½λ©, CSP λ° κΈ°ν 보μ λͺ¨λ² μ¬λ‘λ₯Ό κ²°ν©ν λ€μΈ΅μ μΈ μ κ·Ό λ°©μμ μ±ννλ κ²μ μμ§ λ§μΈμ. μμ νκ³ κ²¬κ³ ν μΉ μ ν리μΌμ΄μ μ ꡬμΆνκΈ° μν΄ μ΅μ 보μ μνκ³Ό μν κΈ°μ μ λν΄ κ³μ λ°°μ°κ³ μ΅μ μ 보λ₯Ό μ μ§νμΈμ.
μ΄ κ°μ΄λλ XSS λ°©μ΄ λ° CSPμ λν κΈ°μ΄μ μΈ μ΄ν΄λ₯Ό μ 곡ν©λλ€. 보μμ μ§μμ μΈ κ³Όμ μ΄λ©°, μ μ¬μ μΈ μνμ μμ λκ°κΈ° μν΄μλ μ§μμ μΈ νμ΅μ΄ νμμ μ΄λΌλ μ μ κΈ°μ΅νμΈμ. μ΄λ¬ν λͺ¨λ² μ¬λ‘λ₯Ό ꡬνν¨μΌλ‘μ¨ μ¬μ©μλ₯Ό μν΄ λ μμ νκ³ μ λ’°ν μ μλ μΉ κ²½νμ λ§λ€ μ μμ΅λλ€.